#ifndef MOLECULEVISITORS_HEADER_VIZMF_PROJECT
#define MOLECULEVISITORS_HEADER_VIZMF_PROJECT

#include "src/core/workspace/molecule/molecule.h"
#include "src/core/scene/abstractmolecularvisitor.h"
#include "src/core/scene/visitorparameters.h"
#include "src/core/scene/sceneelements.h"

#define WF_MAGIC_RADIUS 0.03

class WireframeVisitor : public MolecularVisitor {
public:
  typedef VisitorParameters visitor_params_type;

  WireframeVisitor(ItemWidget* scene, visitor_params_type visitor_params) :
      scene_(scene),  visitor_params_(visitor_params) {}
  ~WireframeVisitor(){}

  void drawAtom(Vector3 pos, Atom::int_type element, Atom::atom_type_type attype, bool selected = false){
//    scene_ ->drawSphere(pos, WF_MAGIC_RADIUS, Material(0.6, 0.6, 0.6, 1.0));
    scene_ ->drawSphere(pos, WF_MAGIC_RADIUS, visitor_params_.atomParameters[element].material);
  }

  void drawBond(Vector3 from, Vector3 to, Atom::int_type elem_from, Atom::int_type elem_to,
                          Atom::atom_type_type attyp_from, Atom::atom_type_type attyp_to,
                          Molecule::bond_type::int_type bond_order, bool selected = false)
  {
    scene_ ->drawCylinder(from, from + ((to - from)/2), WF_MAGIC_RADIUS*bond_order, WF_MAGIC_RADIUS*bond_order, visitor_params_.atomParameters[elem_from].material);
    scene_ ->drawCylinder(from + ((to - from)/2), to, WF_MAGIC_RADIUS*bond_order, WF_MAGIC_RADIUS*bond_order, visitor_params_.atomParameters[elem_to].material );
  }

protected:
  //SceneElements& scene_elements_;
  ItemWidget* scene_;
  visitor_params_type visitor_params_;

};

class ProportionalWireframeVisitor : public WireframeVisitor {
public:
  typedef WireframeVisitor::visitor_params_type visitor_params_type;

  ProportionalWireframeVisitor(ItemWidget* scene, visitor_params_type visitor_params, float proportion = 1.0) :
    WireframeVisitor(scene, visitor_params), proportion_(proportion) {}
  ~ProportionalWireframeVisitor() {}

  void drawAtom(Vector3 pos, Atom::int_type element, Atom::atom_type_type attype, bool selected = false){
    scene_ ->drawSphere(pos, WF_MAGIC_RADIUS * proportion_, visitor_params_.atomParameters[element].material);
  }

  void drawBond(Vector3 from, Vector3 to, Atom::int_type elem_from, Atom::int_type elem_to,
                          Atom::atom_type_type attyp_from, Atom::atom_type_type attyp_to,
                          Molecule::bond_type::int_type bond_order, bool selected = false)
  {
    scene_ ->drawCylinder(from, from + ((to - from)/2), WF_MAGIC_RADIUS*bond_order*proportion_, WF_MAGIC_RADIUS*bond_order*proportion_, visitor_params_.atomParameters[elem_from].material);
    scene_ ->drawCylinder(from + ((to - from)/2), to, WF_MAGIC_RADIUS*bond_order*proportion_, WF_MAGIC_RADIUS*bond_order*proportion_, visitor_params_.atomParameters[elem_to].material );
  }

protected:
  float proportion_;
};

// the same as above...
class CPKVisitor : public MolecularVisitor {};
class BallsSticksVisitor : public MolecularVisitor {};
class LeanVisitor : public MolecularVisitor {};

// shulga: I hope this is realy just a stub for testing purposes, since it's aweful (sorry) from the viewpoint of OO design
class CommonVisitor : public MolecularVisitor
{
public:
  CommonVisitor(ItemWidget* se, const VisitorParameters& visitor_params = VisitorParameters() , const std::string& styleName = "") : widget(se), visitor_params_(visitor_params)
  {
    for (unsigned int i = 0, e = visitor_params_.styleParameters.size(); i!= e; ++i)
    {
      if ( ! visitor_params_.styleParameters[i].name.compare(styleName) )
      {
        style = i;
        break;
      }
    }
  }

  void drawAtom(Vector3 pos, Atom::int_type element, Atom::atom_type_type attype, bool selected = false)
  {
    for ( unsigned int i = 0, e = visitor_params_.styleParameters[style].atom_primitives.size(); i != e; ++i )
    {
      switch(visitor_params_.styleParameters[style].atom_primitives[i])
      {
      case Scene3DElement::sphere:
        widget->drawSphere(pos, visitor_params_.atomParameters[element].radius * visitor_params_.styleParameters[style].atom_parameters[i][0], visitor_params_.atomParameters[element].material);
        break;
      case Scene3DElement::point:
        // since currently we can't draw point :)
        widget->drawSphere(pos, 0.001, visitor_params_.atomParameters[element].material);
        break;
      case Scene3DElement::unknown:
      case Scene3DElement::parallelepiped:
      case Scene3DElement::surface:
      case Scene3DElement::cylinder:
      case Scene3DElement::cone:
      case Scene3DElement::line:
        break;
      }
    }
  }
  void drawBond(Vector3 from, Vector3 to, Atom::int_type elem_from, Atom::int_type elem_to,
                              Atom::atom_type_type attyp_from, Atom::atom_type_type attyp_to,
                              Molecule::bond_type::int_type bond_order, bool selected = false)
  {
    for ( unsigned int i = 0, e = visitor_params_.styleParameters[style].bond_primitives.size(); i != e; ++i )
    {
      switch(visitor_params_.styleParameters[style].bond_primitives[i])
      {
      case Scene3DElement::cylinder:
        if ( visitor_params_.styleParameters[style].bond_parameters[i][1] == 1.0 )
        {
          widget->drawCylinder(from, from + ((to - from)/2), visitor_params_.styleParameters[style].bond_parameters[i][0] * bond_order , visitor_params_.styleParameters[style].bond_parameters[i][0] * bond_order, visitor_params_.atomParameters[elem_from].material);
          widget->drawCylinder(from + ((to - from)/2), to, visitor_params_.styleParameters[style].bond_parameters[i][0] * bond_order , visitor_params_.styleParameters[style].bond_parameters[i][0] * bond_order, visitor_params_.atomParameters[elem_to].material);
        } else {
          widget->drawCylinder(from, to, visitor_params_.styleParameters[style].bond_parameters[i][0] * bond_order , visitor_params_.styleParameters[style].bond_parameters[i][0] * bond_order, Material(0.6, 0.6, 0.6, 1.0));
        }
        break;
      case Scene3DElement::line:
        // since currently we can't draw lines
        if ( visitor_params_.styleParameters[style].bond_parameters[i][0] == 1.0 )
        {
          widget->drawCylinder(from, from + ((to - from)/2), 0.01, 0.01, visitor_params_.atomParameters[elem_from].material);
          widget->drawCylinder(from + ((to - from)/2), to, 0.01, 0.01, visitor_params_.atomParameters[elem_to].material);
        } else {
          widget->drawCylinder(from, to, visitor_params_.styleParameters[style].bond_parameters[i][0] * bond_order , visitor_params_.styleParameters[style].bond_parameters[i][0] * bond_order, Material(0.6, 0.6, 0.6, 1.0));
        }
        break;
      case Scene3DElement::unknown:
      case Scene3DElement::sphere:
      case Scene3DElement::parallelepiped:
      case Scene3DElement::surface:
      case Scene3DElement::point:
      case Scene3DElement::cone:
        break;
      }
    }
  }
private:
  unsigned int style;
  ItemWidget* widget;
  const VisitorParameters& visitor_params_;
};

#endif // MOLECULEVISITORS_H
